#include "apue.h"
#include <ctype.h>
#include <fcntl.h>
#include <aio.h>
#include <errno.h>

#define BSZ 4096
#define NBUF 8

enum rwop {
    UNUSED = 0,
    RD_PENDING= 1,
    WR_PENDING= 2,
};

struct buf {
    enum rwop op;
    char last;
    struct aiocb aiocb;
    unsigned char data[BSZ];
};

struct buf bufs[NBUF];
unsigned char translate(unsigned char c) {
    if (isalpha(c)) {
        if (c >= 'n') 
            c-= 13;
        else if (c >= 'a')
            c+= 13;
        else if (c >= 'N')
            c-= 13;
        else 
            c+= 13;
    }
    return c;
}

int main(int argc, char **argv) {
    int ifd, ofd, j, n, err, numop;
    struct stat sbuf;
    const struct aiocb *aiolist[NBUF];
    off_t off= 0;

    if (argc != 3)
        err_quit("Usage: rot13 infile outfile");
    if ((ifd= open(argv[1], O_RDONLY)) < 0)
        err_sys("cant open %s", argv[1]);
    if ((ofd= open(argv[2], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) < 0)
        err_sys("cant open %s", argv[2]);
    if (fstat(ifd, &sbuf))
        err_sys("cant fstat %s", argv[1]);

    for (int i=0; i<NBUF; ++i) {
        bufs[i].op= UNUSED;
        bufs[i].aiocb.aio_buf= bufs[i].data;
        bufs[i].aiocb.aio_sigevent.sigev_notify= SIGEV_NONE;
        aiolist[i]= NULL;        
    }
    numop=0;

    while(1) {
        for (int i=0; i<NBUF; ++i) {
            switch (bufs[i].op) {
                case UNUSED:
                    if (off < sbuf.st_size) {
                        bufs[i].op= RD_PENDING;
                        bufs[i].aiocb.aio_fildes= ifd;
                        bufs[i].aiocb.aio_offset= off;
                        bufs[i].aiocb.aio_nbytes= BSZ;
                        off+= BSZ;
                        if (off >= sbuf.st_size)
                            bufs[i].last= 1;

                        if (aio_read(&bufs[i].aiocb) < 0)
                            err_sys("aio_read error");

                        aiolist[i]= &bufs[i].aiocb;
                        numop++;
                    }
                    break; 

                case RD_PENDING:
                    if ((err= aio_error(&bufs[i].aiocb)) != EINPROGRESS) {
                        if (err != 0) {
                            if (err == -1)
                                err_sys("aio_error error");
                            else 
                                err_exit(errno, "read error");
                        }
                        /* A read is complete; translate the buffer and write it. */
                        if ((n= aio_return(&bufs[i].aiocb)) < 0)
                            err_sys("aio_return error");
                        if (n != BSZ && bufs[i].last != 1)
                            err_quit("short read error(%d/%d)", n, BSZ);

                        for (j=0; j<n; j++)
                            bufs[i].data[j]= translate(bufs[i].data[j]);
                        bufs[i].op= WR_PENDING;
                        bufs[i].aiocb.aio_fildes= ofd;
                        bufs[i].aiocb.aio_nbytes= n;
                        if (aio_write(&bufs[i].aiocb) < 0)
                            err_sys("aio_write error");
                    }
                    break;

                case WR_PENDING:
                    if ((err= aio_error(&bufs[i].aiocb)) != EINPROGRESS) {
                        if (err != 0) {
                            if (err == -1)
                                err_sys("aio_error error");
                            else
                                err_exit(err, "__write error");
                        }

                        /** A write is complete; mark the buffer as unused */
                        if ((n= aio_return(&bufs[i].aiocb)) < 0)
                            err_sys("aio return error");
                        if (n != bufs[i].aiocb.aio_nbytes)
                            err_quit("aio read error");

                        // reset state 
                        bufs[i].op= UNUSED;
                        aiolist[i]= NULL;
                        numop--;
                    }
                    break;
            }
        }

        if (numop == 0) {
            if (off >= sbuf.st_size) {
                break;
            }
        } else {
            if (aio_suspend(aiolist, NBUF, NULL) < 0)
                err_sys("aio_suspend error");
        }
    }

    bufs[0].aiocb.aio_fildes= ofd;
    if (aio_fsync(O_SYNC, &bufs[0].aiocb) < 0)
        err_sys("aio_fsync error");

    exit(0);
}

